import { execSync } from "node:child_process";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import {
  CommunicationWay,
  ServiceHandler,
  ServiceRegister,
} from "@main/shared/reflect";
import { TYPES } from "@main/shared/types";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import {
  SSEClientTransport,
  type SSEClientTransportOptions,
} from "@modelcontextprotocol/sdk/client/sse.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import {
  StreamableHTTPClientTransport,
  type StreamableHTTPClientTransportOptions,
} from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import logger from "@shared/logger/main-logger";
import type {
  CreateMcpServerData,
  McpServer,
  McpTool,
  UpdateMcpServerData,
} from "@shared/triplit/types";
import type { OpenAITools } from "@shared/types/openai";
import type {
  ContentBlock,
  SequentialMessageContent,
  ToolCallContentBlock,
} from "@shared/types/sequential-content";
import { inject, injectable } from "inversify";
import * as cron from "node-cron";
import type { McpServerDbService } from "./db-service/mcp-server-db-service";
import type { McpToolDbService } from "./db-service/mcp-tool-db-service";
import type { ThreadMcpServerDbService } from "./db-service/thread-mcp-server-db-service";
import { EventNames, emitter, sendToRenderer } from "./event-service";
import type { MessageService } from "./message-service";

/**
 * Represents an active MCP client connection with transport details.
 * Each server type requires different transport mechanisms and connection management.
 */
interface McpClientInfo {
  client: Client; // MCP SDK client instance
  transport: // Transport layer specific to server type
    | StdioClientTransport // For local processes (command + args)
    | SSEClientTransport // For server-sent events over HTTP
    | StreamableHTTPClientTransport; // For bidirectional HTTP streaming
  serverId: string; // Database ID of the MCP server
  serverType: "stdio" | "sse" | "streamableHTTP";
  connected: boolean; // Live connection status
}

/**
 * Parsed command information for STDIO-based MCP servers.
 * Handles complex command parsing with environment variables.
 */
interface StdioCommandInfo {
  command: string; // Original command (first part)
  resolvedCommand: string; // Full path to executable
  args: string[]; // Command arguments (space-separated)
  env: Record<string, string>; // Merged environment variables (system + custom)
}

/**
 * McpService manages the complete lifecycle of Model Context Protocol (MCP) connections.
 *
 * This service handles:
 * - Multiple transport types (STDIO, SSE, StreamableHTTP)
 * - Automatic client connection/reconnection management
 * - Tool discovery and synchronization across all connected servers
 * - Thread-specific server associations for context-aware tool access
 * - Real-time tool availability updates via cron-based synchronization
 *
 * The service maintains a global map of active client connections and ensures
 * tools are always available for AI model function calling.
 */
@injectable()
@ServiceRegister(TYPES.McpService)
export class McpService {
  /** Global registry of active MCP client connections, keyed by server ID */
  private mcpClients: Map<string, McpClientInfo> = new Map();
  /** Scheduled task for automatic tool synchronization (runs every minute) */
  private cronJob?: cron.ScheduledTask;

  constructor(
    @inject(TYPES.McpServerDbService)
    private mcpServerDbService: McpServerDbService,
    @inject(TYPES.McpToolDbService)
    private mcpToolDbService: McpToolDbService,
    @inject(TYPES.ThreadMcpServerDbService)
    private threadMcpServerDbService: ThreadMcpServerDbService,
    @inject(TYPES.MessageService)
    private messageService: MessageService,
  ) {
    this.initializeMcpService().catch((err) => {
      logger.error("Failed to initialize MCP service after database init", {
        error: err,
      });
    });
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async insertMcpServer(
    _event: Electron.IpcMainEvent,
    data: CreateMcpServerData,
  ): Promise<McpServer> {
    try {
      logger.info("Inserting MCP server", {
        name: data.name,
        type: data.type,
        url: data.url,
        hasAdvancedSettings: !!data.advancedSettings,
        customHeaders: data.advancedSettings?.customHeaders,
        customEnvVars: data.advancedSettings?.customEnvVars,
      });

      const result = await this.mcpServerDbService.insertMcpServer(data);

      logger.info("MCP server inserted successfully", {
        id: result.id,
        name: result.name,
      });

      return result;
    } catch (err) {
      logger.error("Failed to insert MCP server", {
        error: err,
        data: data,
      });
      throw err;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async updateMcpServer(
    _event: Electron.IpcMainEvent,
    serverId: string,
    updateData: UpdateMcpServerData,
  ): Promise<void> {
    try {
      logger.info("Updating MCP server", {
        serverId,
        name: updateData.name,
        type: updateData.type,
        url: updateData.url,
        hasAdvancedSettings: !!updateData.advancedSettings,
        customHeaders: updateData.advancedSettings?.customHeaders,
        customEnvVars: updateData.advancedSettings?.customEnvVars,
      });

      await this.mcpServerDbService.updateMcpServer(serverId, updateData);

      logger.info("MCP server updated successfully", {
        serverId,
        name: updateData.name,
      });
    } catch (err) {
      logger.error("Failed to update MCP server", {
        error: err,
        serverId,
        updateData: updateData,
      });
      throw err;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async deleteMcpServer(
    _event: Electron.IpcMainEvent,
    serverId: string,
  ): Promise<void> {
    try {
      logger.info("Deleting MCP server and all associated data", { serverId });

      // 1. Disconnect from the server if it's currently connected
      try {
        await this.disconnectFromMcpServer(serverId);
        logger.info("Disconnected from MCP server before deletion", {
          serverId,
        });
      } catch (err) {
        logger.warn("Failed to disconnect MCP server before deletion", {
          serverId,
          error: err,
        });
        // Continue with deletion even if disconnect fails
      }

      // 2. Delete all tools associated with this server
      try {
        await this.mcpToolDbService.clearMcpTools(serverId);
        logger.info("Deleted all MCP tools for server", { serverId });
      } catch (err) {
        logger.error("Failed to delete MCP tools", {
          serverId,
          error: err,
        });
        throw err;
      }

      // 3. Delete all thread-server associations for this server
      try {
        await this.threadMcpServerDbService.deleteAllAssociationsForServer(
          serverId,
        );
        logger.info("Deleted all thread-server associations", { serverId });
      } catch (err) {
        logger.error("Failed to delete thread-server associations", {
          serverId,
          error: err,
        });
        throw err;
      }

      // 4. Finally delete the server itself
      await this.mcpServerDbService.deleteMcpServer(serverId);

      logger.info("Successfully deleted MCP server and all associated data", {
        serverId,
      });
    } catch (err) {
      logger.error("Failed to delete MCP server", {
        serverId,
        error: err,
      });
      throw err;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getMcpServerById(
    _event: Electron.IpcMainEvent,
    serverId: string,
  ): Promise<McpServer | null> {
    return await this.getMcpServerByIdInternal(serverId);
  }

  /**
   * Internal method to get MCP server by ID (for use within main process)
   */
  async getMcpServerByIdInternal(serverId: string): Promise<McpServer | null> {
    return await this.mcpServerDbService.getMcpServerById(serverId);
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async enableMcpServer(
    _event: Electron.IpcMainEvent,
    serverId: string,
    enabled: boolean,
  ): Promise<void> {
    return await this.mcpServerDbService.enableMcpServer(serverId, enabled);
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async updateMcpServerOrder(
    _event: Electron.IpcMainEvent,
    serverId: string,
    order: number,
  ): Promise<void> {
    return await this.mcpServerDbService.updateMcpServerOrder(serverId, order);
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getMcpServersByType(
    _event: Electron.IpcMainEvent,
    type: "stdio" | "sse" | "streamableHTTP",
  ): Promise<McpServer[]> {
    return await this.mcpServerDbService.getMcpServersByType(type);
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getEnabledMcpServers(
    _event: Electron.IpcMainEvent,
  ): Promise<McpServer[]> {
    return await this.mcpServerDbService.getEnabledMcpServers();
  }

  // Global MCP client management methods

  /**
   * Connect to an MCP server and establish a persistent client connection.
   *
   * This method handles the complex connection lifecycle:
   * - Validates server configuration and enabled status
   * - Creates appropriate transport based on server type (STDIO/SSE/HTTP)
   * - Establishes MCP client connection using the Model Context Protocol
   * - Stores connection info in the global registry for tool execution
   * - Provides detailed logging for connection troubleshooting
   *
   * Connection failures are gracefully handled - the server remains in the
   * database but won't be available for tool execution until reconnected.
   *
   * @param serverId - Database ID of the MCP server to connect to
   */
  async connectToMcpServer(serverId: string): Promise<void> {
    try {
      // Prevent duplicate connections - check existing client status
      if (this.mcpClients.has(serverId)) {
        const clientInfo = this.mcpClients.get(serverId);
        if (clientInfo?.connected) {
          logger.info("MCP server already connected", { serverId });
          return;
        }
      }

      // Fetch server configuration from database
      const server = await this.mcpServerDbService.getMcpServerById(serverId);
      if (!server) {
        throw new Error(`MCP server not found: ${serverId}`);
      }

      // Skip disabled servers - they shouldn't be connected
      if (!server.enabled) {
        logger.info("MCP server is disabled, skipping connection", {
          serverId,
          name: server.name,
        });
        return;
      }

      logger.info("Connecting to MCP server", {
        serverId,
        name: server.name,
        type: server.type,
        url: server.url,
        command: server.command,
      });

      // Create transport-specific connection based on server type
      const transport = this.createTransport(server);
      const client = new Client({
        name: "302-AI-Studio", // Client identifier for MCP protocol
        version: "1.0.0",
      });

      // Establish MCP protocol connection
      await client.connect(transport);

      // Store successful connection in global registry
      const clientInfo: McpClientInfo = {
        client,
        transport,
        serverId,
        serverType: server.type,
        connected: true,
      };

      this.mcpClients.set(serverId, clientInfo);
      logger.info("Successfully connected to MCP server", {
        serverId,
        name: server.name,
      });
    } catch (err) {
      logger.error("Failed to connect to MCP server", {
        serverId,
        error: err,
      });
      // Remove failed connection from map
      this.mcpClients.delete(serverId);

      // Enhance error message with context
      const enhancedError = new Error(
        this.extractErrorMessage(err, `MCP Server Connection [${serverId}]`),
      );
      throw enhancedError;
    }
  }

  /**
   * Disconnect from an MCP server and remove it from the global clients map
   */
  async disconnectFromMcpServer(serverId: string): Promise<void> {
    try {
      const clientInfo = this.mcpClients.get(serverId);
      if (!clientInfo) {
        logger.info("MCP server not connected", { serverId });
        return;
      }

      logger.info("Disconnecting from MCP server", { serverId });

      // Close transport connection
      if (
        clientInfo.transport &&
        typeof clientInfo.transport.close === "function"
      ) {
        try {
          await clientInfo.transport.close();
        } catch (err) {
          logger.warn("Error closing transport", { serverId, error: err });
        }
      }

      // Remove from map
      this.mcpClients.delete(serverId);
      logger.info("Successfully disconnected from MCP server", { serverId });
    } catch (err) {
      logger.error("Failed to disconnect from MCP server", {
        serverId,
        error: err,
      });
      throw err;
    }
  }

  /**
   * Get tools from a specific MCP server
   */
  async getToolsFromServer(serverId: string): Promise<
    Array<{
      name: string;
      description?: string;
      inputSchema?: Record<string, unknown>;
    }>
  > {
    const clientInfo = this.mcpClients.get(serverId);
    if (!clientInfo || !clientInfo.connected) {
      throw new Error(`MCP server not connected: ${serverId}`);
    }

    try {
      const toolsResponse = await clientInfo.client.listTools();
      return toolsResponse.tools || [];
    } catch (err) {
      logger.error("Failed to get tools from MCP server", {
        serverId,
        error: err,
      });
      throw err;
    }
  }

  /**
   * Synchronizes available tools from a connected MCP server to the local database.
   *
   * This process involves:
   * - Fetching the current tool list from the live MCP server
   * - Clearing stale tool entries from the database (prevents orphaned tools)
   * - Inserting fresh tool definitions with parameters and descriptions
   * - Enabling all discovered tools by default for immediate availability
   *
   * Tool synchronization is critical for keeping the AI model's function
   * calling capabilities in sync with what's actually available from servers.
   *
   * @param serverId - Database ID of the connected MCP server to sync
   */
  async syncToolsFromServer(serverId: string): Promise<void> {
    try {
      const clientInfo = this.mcpClients.get(serverId);
      if (!clientInfo || !clientInfo.connected) {
        logger.warn("Cannot sync tools from disconnected server", { serverId });
        return;
      }

      logger.info("Syncing tools from MCP server", { serverId });

      // Fetch live tool definitions from the connected server
      const tools = await this.getToolsFromServer(serverId);

      // Clear all existing tools for this server to prevent stale entries
      // This ensures we have a clean slate for the current server state
      await this.mcpToolDbService.clearMcpTools(serverId);

      // Insert fresh tool definitions into the database
      if (tools.length > 0) {
        for (const tool of tools) {
          try {
            await this.mcpToolDbService.insertMcpTool({
              mcpServerId: serverId,
              name: tool.name,
              description: tool.description || "",
              parameters: tool.inputSchema || {},
              enabled: true, // Enable by default for immediate availability
            });
          } catch (err) {
            logger.error("Failed to insert MCP tool", {
              serverId,
              toolName: tool.name,
              error: err,
            });
          }
        }
      }

      logger.info("Successfully synced tools from MCP server", {
        serverId,
        toolCount: tools.length,
      });
    } catch (err) {
      logger.error("Failed to sync tools from MCP server", {
        serverId,
        error: err,
      });
      throw err;
    }
  }

  /**
   * Smart synchronization that preserves existing tool IDs when possible
   * This prevents ID changes during tool execution by updating existing tools
   * instead of clearing and recreating them
   */
  async smartSyncToolsFromServer(serverId: string): Promise<void> {
    try {
      const clientInfo = this.mcpClients.get(serverId);
      if (!clientInfo || !clientInfo.connected) {
        logger.warn("Cannot sync tools from disconnected server", { serverId });
        return;
      }

      logger.info("Smart syncing tools from MCP server", { serverId });

      // Fetch live tool definitions from the connected server
      const liveTools = await this.getToolsFromServer(serverId);

      // Get existing tools from database
      const existingTools =
        await this.mcpToolDbService.getMcpToolsByServerId(serverId);

      // Create maps for efficient lookup
      const liveToolMap = new Map(liveTools.map((tool) => [tool.name, tool]));
      const existingToolMap = new Map(
        existingTools.map((tool) => [tool.name, tool]),
      );

      // Update existing tools and add new ones
      for (const liveTool of liveTools) {
        const existingTool = existingToolMap.get(liveTool.name);

        if (existingTool) {
          // Update existing tool if data has changed
          const needsUpdate =
            existingTool.description !== (liveTool.description || "") ||
            JSON.stringify(existingTool.parameters) !==
              JSON.stringify(liveTool.inputSchema || {});

          if (needsUpdate) {
            logger.info("Updating existing MCP tool", {
              serverId,
              toolName: liveTool.name,
              toolId: existingTool.id,
            });

            await this.mcpToolDbService.updateMcpTool(existingTool.id, {
              description: liveTool.description || "",
              parameters: liveTool.inputSchema || {},
            });
          }
        } else {
          // Add new tool
          logger.info("Adding new MCP tool", {
            serverId,
            toolName: liveTool.name,
          });

          try {
            await this.mcpToolDbService.insertMcpTool({
              mcpServerId: serverId,
              name: liveTool.name,
              description: liveTool.description || "",
              parameters: liveTool.inputSchema || {},
              enabled: true,
            });
          } catch (err) {
            logger.error("Failed to insert new MCP tool", {
              serverId,
              toolName: liveTool.name,
              error: err,
            });
          }
        }
      }

      // Remove tools that no longer exist on the server
      for (const existingTool of existingTools) {
        if (!liveToolMap.has(existingTool.name)) {
          logger.info("Removing obsolete MCP tool", {
            serverId,
            toolName: existingTool.name,
            toolId: existingTool.id,
          });

          try {
            await this.mcpToolDbService.deleteMcpTool(existingTool.id);
          } catch (err) {
            logger.error("Failed to delete obsolete MCP tool", {
              serverId,
              toolName: existingTool.name,
              toolId: existingTool.id,
              error: err,
            });
          }
        }
      }

      logger.info("Successfully smart synced tools from MCP server", {
        serverId,
        liveToolCount: liveTools.length,
        existingToolCount: existingTools.length,
      });
    } catch (err) {
      logger.error("Failed to smart sync tools from MCP server", {
        serverId,
        error: err,
      });
      throw err;
    }
  }

  /**
   * Sync tools from all connected servers
   */
  async syncToolsFromAllServers(): Promise<void> {
    logger.info("Starting sync of tools from all connected MCP servers");

    const connectedServerIds = Array.from(this.mcpClients.keys());
    const results = await Promise.allSettled(
      connectedServerIds.map((serverId) => this.syncToolsFromServer(serverId)),
    );

    let successCount = 0;
    let errorCount = 0;

    results.forEach((result, index) => {
      const serverId = connectedServerIds[index];
      if (result.status === "fulfilled") {
        successCount++;
      } else {
        errorCount++;
        logger.error("Failed to sync tools from server", {
          serverId,
          error: result.reason,
        });
      }
    });

    logger.info("Completed sync of tools from all connected MCP servers", {
      totalServers: connectedServerIds.length,
      successCount,
      errorCount,
    });
  }

  /**
   * Smart sync tools from all connected servers (preserves tool IDs)
   */
  async smartSyncToolsFromAllServers(): Promise<void> {
    logger.info("Starting smart sync of tools from all connected MCP servers");

    const connectedServerIds = Array.from(this.mcpClients.keys());
    const results = await Promise.allSettled(
      connectedServerIds.map((serverId) =>
        this.smartSyncToolsFromServer(serverId),
      ),
    );

    let successCount = 0;
    let errorCount = 0;

    results.forEach((result, index) => {
      const serverId = connectedServerIds[index];
      if (result.status === "fulfilled") {
        successCount++;
      } else {
        errorCount++;
        logger.error("Failed to smart sync tools from server", {
          serverId,
          error: result.reason,
        });
      }
    });

    logger.info(
      "Completed smart sync of tools from all connected MCP servers",
      {
        totalServers: connectedServerIds.length,
        successCount,
        errorCount,
      },
    );
  }

  /**
   * Setup automatic tool synchronization using node-cron
   */
  private setupAutoSync(): void {
    // Schedule to run every minute
    this.cronJob = cron.schedule("*/1 * * * *", async () => {
      try {
        logger.info("Running scheduled smart tool sync from all MCP servers");
        await this.smartSyncToolsFromAllServers();
      } catch (err) {
        logger.error("Scheduled tool sync failed", { error: err });
      }
    });

    // Don't start immediately
    this.cronJob.stop();
    logger.info("Auto-sync cron job configured");
  }

  /**
   * Start the auto-sync cron job
   */
  startAutoSync(): void {
    if (this.cronJob) {
      this.cronJob.start();
      logger.info("Auto-sync cron job started");
    }
  }

  /**
   * Stop the auto-sync cron job
   */
  stopAutoSync(): void {
    if (this.cronJob) {
      this.cronJob.stop();
      logger.info("Auto-sync cron job stopped");
    }
  }

  /**
   * Initialize MCP service - connect to enabled servers and start auto-sync
   */
  async initializeMcpService(): Promise<void> {
    try {
      logger.info("Initializing MCP service");

      // Setup auto-sync
      this.setupAutoSync();

      // Connect to all enabled servers
      logger.info("Fetching enabled MCP servers from database");
      const enabledServers =
        await this.mcpServerDbService.getEnabledMcpServers();

      logger.info("Found enabled MCP servers", {
        count: enabledServers.length,
        servers: enabledServers.map((s) => ({
          id: s.id,
          name: s.name,
          type: s.type,
          enabled: s.enabled,
          url: s.url,
          command: s.command,
        })),
      });

      if (enabledServers.length === 0) {
        logger.warn("No enabled MCP servers found in database");
        logger.info("MCP service initialized with no servers", {
          connectedServers: 0,
          totalEnabledServers: 0,
        });
        return;
      }

      const connectionPromises = enabledServers.map(async (server) => {
        try {
          logger.info("Attempting to connect to MCP server", {
            serverId: server.id,
            serverName: server.name,
            serverType: server.type,
          });
          await this.connectToMcpServer(server.id);
          logger.info("Successfully connected to MCP server", {
            serverId: server.id,
            serverName: server.name,
          });
        } catch (err) {
          logger.error(
            "Failed to connect to enabled MCP server during initialization",
            {
              serverId: server.id,
              serverName: server.name,
              error: err,
            },
          );
        }
      });

      await Promise.allSettled(connectionPromises);

      // Start auto-sync
      this.startAutoSync();

      // Initial sync
      await this.syncToolsFromAllServers();

      logger.info("MCP service initialized successfully", {
        connectedServers: this.mcpClients.size,
        totalEnabledServers: enabledServers.length,
      });
    } catch (err) {
      logger.error("Failed to initialize MCP service", { error: err });
      throw err;
    }
  }

  /**
   * Cleanup MCP service - disconnect all servers and stop auto-sync
   */
  async cleanupMcpService(): Promise<void> {
    try {
      logger.info("Cleaning up MCP service");

      // Stop auto-sync
      this.stopAutoSync();

      // Disconnect all servers
      const disconnectionPromises = Array.from(this.mcpClients.keys()).map(
        async (serverId) => {
          try {
            await this.disconnectFromMcpServer(serverId);
          } catch (err) {
            logger.error("Failed to disconnect MCP server during cleanup", {
              serverId,
              error: err,
            });
          }
        },
      );

      await Promise.allSettled(disconnectionPromises);

      logger.info("MCP service cleanup completed");
    } catch (err) {
      logger.error("Failed to cleanup MCP service", { error: err });
      throw err;
    }
  }

  // Public API methods for frontend

  /**
   * Manual refresh of all tools from connected servers
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async refreshAllTools(_event: Electron.IpcMainEvent): Promise<void> {
    logger.info("Manual tool refresh requested");
    await this.syncToolsFromAllServers();
  }

  /**
   * Get connected MCP clients info
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getConnectedClients(_event: Electron.IpcMainEvent): Promise<
    Array<{
      serverId: string;
      serverType: string;
      connected: boolean;
    }>
  > {
    return Array.from(this.mcpClients.entries()).map(
      ([serverId, clientInfo]) => ({
        serverId,
        serverType: clientInfo.serverType,
        connected: clientInfo.connected,
      }),
    );
  }

  /**
   * Get connection status for a specific server
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getClientConnectionStatus(
    _event: Electron.IpcMainEvent,
    serverId: string,
  ): Promise<boolean> {
    const clientInfo = this.mcpClients.get(serverId);
    return clientInfo?.connected || false;
  }

  /**
   * Manually connect to a specific MCP server
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async connectToMcpServerManual(
    _event: Electron.IpcMainEvent,
    serverId: string,
  ): Promise<void> {
    await this.connectToMcpServer(serverId);
  }

  /**
   * Manually disconnect from a specific MCP server
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async disconnectFromMcpServerManual(
    _event: Electron.IpcMainEvent,
    serverId: string,
  ): Promise<void> {
    await this.disconnectFromMcpServer(serverId);
  }

  /**
   * Get thread-MCP server associations by thread ID
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getThreadMcpServersByThreadId(
    _event: Electron.IpcMainEvent,
    threadId: string,
  ): Promise<import("@shared/triplit/types").ThreadMcpServer[]> {
    try {
      const threadMcpServers =
        await this.threadMcpServerDbService.getThreadMcpServersByThreadId(
          threadId,
        );
      return threadMcpServers || [];
    } catch (err) {
      logger.error("Failed to get thread MCP servers", {
        threadId,
        error: err,
      });
      throw err;
    }
  }

  /**
   * Get MCP tools for a specific thread as OpenAI function calls
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getMcpToolsForThread(
    _event: Electron.IpcMainEvent,
    threadId: string,
  ): Promise<OpenAITools> {
    try {
      // Get enabled MCP servers associated with this thread
      const threadMcpServers =
        await this.threadMcpServerDbService.getEnabledThreadMcpServersByThreadId(
          threadId,
        );

      if (!threadMcpServers || threadMcpServers.length === 0) {
        logger.debug("No MCP servers associated with thread", { threadId });
        return [];
      }

      // Extract server IDs
      const serverIds = threadMcpServers.map((tms) => tms.mcpServerId);

      logger.info("Getting MCP tools for thread", {
        threadId,
        serverIds,
        serverCount: serverIds.length,
      });

      // Use existing method to get tools for these servers
      return await this.getMcpToolsAsOpenAIFunctions(serverIds);
    } catch (err) {
      logger.error("Failed to get MCP tools for thread", {
        threadId,
        error: err,
      });
      throw err;
    }
  }

  /**
   * Convert MCP tools to OpenAI Function Call format
   */
  async getMcpToolsAsOpenAIFunctions(
    serverIds?: string[],
  ): Promise<OpenAITools> {
    try {
      let tools: McpTool[] = [];

      if (serverIds && serverIds.length > 0) {
        // Get tools from specific servers
        tools = [];
        for (const serverId of serverIds) {
          const serverTools =
            await this.mcpToolDbService.getEnabledMcpToolsByServerId(serverId);
          tools.push(...serverTools);
        }
      } else {
        // Get all enabled tools from all servers
        const enabledServers =
          await this.mcpServerDbService.getEnabledMcpServers();
        tools = [];
        for (const server of enabledServers) {
          const serverTools =
            await this.mcpToolDbService.getEnabledMcpToolsByServerId(server.id);
          tools.push(...serverTools);
        }
      }

      // Convert to OpenAI function format
      const openAIFunctions = tools.map((tool) => ({
        type: "function" as const,
        function: {
          name: tool.name,
          description: tool.description || `MCP tool: ${tool.name}`,
          parameters: this.convertMcpParametersToOpenAI(tool.parameters),
          strict: true,
        },
      }));

      logger.info("Converted MCP tools to OpenAI function format", {
        toolCount: tools.length,
        serverIds: serverIds || "all",
      });

      return openAIFunctions;
    } catch (err) {
      logger.error("Failed to convert MCP tools to OpenAI functions", {
        error: err,
        serverIds,
      });
      throw err;
    }
  }

  // Private utility methods

  /**
   * Merge system environment variables with custom environment variables.
   * Now reads from user's activated shell to get proper PATH and environment.
   * Falls back to process.env if shell reading fails.
   *
   * @param customEnv - Custom environment variables from user configuration
   * @returns Merged environment variables with shell environment as base
   */
  private mergeEnvironment(
    customEnv?: Record<string, string>,
  ): Record<string, string> {
    // Start with an empty object to avoid undefined values
    const mergedEnv: Record<string, string> = {};

    // Try to get environment from user's shell first
    const shellEnv = this.getShellEnvironment();
    const baseEnv = shellEnv || process.env;

    // Add environment variables from shell or process.env, filtering out undefined values
    for (const [key, value] of Object.entries(baseEnv)) {
      if (value !== undefined) {
        mergedEnv[key] = String(value);
      }
    }

    // Ensure critical Node.js environment variables are set for packaged apps
    if (!mergedEnv.NODE_ENV) {
      mergedEnv.NODE_ENV = "production";
    }

    // For packaged applications, ensure PATH includes common Node.js locations
    if (mergedEnv.PATH) {
      const commonNodePaths = [
        "/usr/local/bin",
        "/usr/bin",
        path.join(os.homedir(), ".local/bin"),
        path.join(os.homedir(), ".local/share/mise/installs/node/*/bin"),
        path.join(os.homedir(), ".local/share/mise/installs/bun/*/bin"),
        path.join(os.homedir(), ".local/share/mise/installs/uv/*/uv-*"), // UV installations
        path.join(os.homedir(), ".nvm/current/bin"),
      ];

      // Expand glob patterns for mise installations
      const expandedPaths: string[] = [];
      for (const pathPattern of commonNodePaths) {
        if (pathPattern.includes("*")) {
          const baseDir = pathPattern.split("*")[0];
          if (fs.existsSync(baseDir)) {
            try {
              const versionDirs = fs.readdirSync(baseDir);
              for (const versionDir of versionDirs) {
                const expandedPath = pathPattern.replace("*", versionDir);
                if (fs.existsSync(expandedPath)) {
                  expandedPaths.push(expandedPath);
                }
              }
            } catch {
              // Ignore glob expansion errors
            }
          }
        } else if (fs.existsSync(pathPattern)) {
          expandedPaths.push(pathPattern);
        }
      }

      // Add expanded paths to PATH if they're not already there
      const pathSeparator = os.platform() === "win32" ? ";" : ":";
      const currentPaths = mergedEnv.PATH.split(pathSeparator);

      logger.debug("PATH expansion debug", {
        originalPath: mergedEnv.PATH,
        expandedPaths: expandedPaths,
        currentPathsCount: currentPaths.length,
      });

      for (const newPath of expandedPaths) {
        if (!currentPaths.includes(newPath)) {
          mergedEnv.PATH = `${newPath}${pathSeparator}${mergedEnv.PATH}`;
          logger.debug("Added path to PATH", {
            newPath: newPath,
            updatedPath: mergedEnv.PATH,
          });
        }
      }
    }

    // Overlay custom environment variables
    if (customEnv) {
      for (const [key, value] of Object.entries(customEnv)) {
        if (value !== undefined) {
          mergedEnv[key] = String(value);
        }
      }
    }

    // Ensure HOME directory is set (critical for Node.js tools)
    if (!mergedEnv.HOME) {
      mergedEnv.HOME = os.homedir();
    }

    // For npx and other Node.js tools, ensure npm-related paths are available
    if (!mergedEnv.npm_config_cache) {
      mergedEnv.npm_config_cache = path.join(os.homedir(), ".npm");
    }

    if (!mergedEnv.npm_config_prefix) {
      // Try to detect npm prefix from PATH
      const npmPaths = mergedEnv.PATH.split(
        os.platform() === "win32" ? ";" : ":",
      ).filter((p) => p.includes("node") && p.includes("bin"));
      if (npmPaths.length > 0) {
        // Use the parent directory of the first Node.js bin path found
        mergedEnv.npm_config_prefix = path.dirname(npmPaths[0]);
      }
    }

    logger.debug("Merged environment for packaged app", {
      hasNODE_ENV: !!mergedEnv.NODE_ENV,
      hasHOME: !!mergedEnv.HOME,
      hasPATH: !!mergedEnv.PATH,
      pathLength: mergedEnv.PATH?.length || 0,
      customEnvCount: Object.keys(customEnv || {}).length,
    });

    return mergedEnv;
  }

  /**
   * Get environment variables from user's activated shell.
   * This ensures we get the full shell environment including PATH modifications
   * from shell configuration files (.bashrc, .zshrc, etc.).
   *
   * @returns Shell environment variables or null if failed
   */
  private getShellEnvironment(): Record<string, string> | null {
    try {
      logger.debug("Attempting to read shell environment");

      // Detect user's shell
      const userShell = this.detectUserShell();
      if (!userShell) {
        logger.warn("Could not detect user shell, falling back to process.env");
        return null;
      }

      logger.debug("Detected user shell", { shell: userShell });

      // Execute shell command to get environment
      const envCommand = this.buildShellEnvCommand(userShell);
      logger.debug("Executing shell environment command", {
        command: envCommand,
      });

      const envOutput = execSync(envCommand, {
        encoding: "utf8",
        timeout: 10000, // 10 second timeout
        stdio: ["ignore", "pipe", "ignore"], // Ignore stderr to avoid noise
        env: process.env, // Use current env as base for the shell execution
      });

      // Parse environment output
      const shellEnv = this.parseShellEnvironment(envOutput);

      logger.debug("Successfully read shell environment", {
        shell: userShell,
        variableCount: Object.keys(shellEnv).length,
        hasPath: !!shellEnv.PATH,
        pathLength: shellEnv.PATH?.length || 0,
      });

      return shellEnv;
    } catch (error) {
      logger.warn(
        "Failed to read shell environment, falling back to process.env",
        {
          error: error instanceof Error ? error.message : String(error),
        },
      );
      return null;
    }
  }

  /**
   * Detect the user's shell by using echo $SHELL command and fallback methods.
   */
  private detectUserShell(): string | null {
    try {
      // First try using echo $SHELL to get the current shell
      const shellPath = execSync("echo $SHELL", {
        encoding: "utf8",
        timeout: 2000,
        stdio: ["ignore", "pipe", "ignore"],
      }).trim();

      if (shellPath && fs.existsSync(shellPath)) {
        logger.debug("Detected shell using echo $SHELL", { shell: shellPath });
        return shellPath;
      }
    } catch (error) {
      logger.debug("Failed to get shell using echo $SHELL", { error });
    }

    // Fallback: try SHELL environment variable
    const shellEnv = process.env.SHELL;
    if (shellEnv && fs.existsSync(shellEnv)) {
      logger.debug("Using SHELL environment variable", { shell: shellEnv });
      return shellEnv;
    }

    // Last resort: try common shell paths
    const commonShells = [
      "/bin/bash",
      "/usr/bin/bash",
      "/bin/zsh",
      "/usr/bin/zsh",
      "/usr/local/bin/bash",
      "/usr/local/bin/zsh",
      "/opt/homebrew/bin/bash",
      "/opt/homebrew/bin/zsh",
    ];

    for (const shell of commonShells) {
      if (fs.existsSync(shell)) {
        logger.debug("Found shell at common path", { shell });
        return shell;
      }
    }

    logger.warn("No shell found in any method");
    return null;
  }

  /**
   * Build shell command to get environment variables.
   * Uses interactive login shell to ensure all shell configuration is loaded.
   */
  private buildShellEnvCommand(shellPath: string): string {
    const shellName = path.basename(shellPath);

    // Use different approaches based on shell type
    switch (shellName) {
      case "bash":
        // Use interactive login shell to load all bash configs
        return `${shellPath} -l -i -c 'env'`;
      case "zsh":
        // Use interactive login shell to load all zsh configs
        return `${shellPath} -l -i -c 'env'`;
      case "fish":
        // Fish has different syntax
        return `${shellPath} -l -i -c 'env'`;
      default:
        // Generic approach for other shells
        return `${shellPath} -l -c 'env'`;
    }
  }

  /**
   * Parse shell environment output into key-value pairs.
   */
  private parseShellEnvironment(envOutput: string): Record<string, string> {
    const env: Record<string, string> = {};
    const lines = envOutput.split("\n");

    for (const line of lines) {
      const trimmedLine = line.trim();
      if (!trimmedLine) continue;

      const equalIndex = trimmedLine.indexOf("=");
      if (equalIndex === -1) continue;

      const key = trimmedLine.substring(0, equalIndex);
      const value = trimmedLine.substring(equalIndex + 1);

      // Skip invalid variable names
      if (!key || key.includes(" ") || key.startsWith("_")) continue;

      env[key] = value;
    }

    return env;
  }

  /**
   * Expand user paths and environment variables in command strings.
   * Handles:
   * - ~ and ~/ for user home directory
   * - ~user for other user directories
   * - $VAR and ${VAR} environment variable expansion
   * - Relative paths (./command, ../command)
   *
   * @param commandPath - Path that may contain user shortcuts
   * @param env - Environment variables for expansion
   * @returns Fully expanded absolute path
   */
  private expandPath(commandPath: string, env: Record<string, string>): string {
    let expandedPath = commandPath;

    try {
      // Handle tilde expansion
      if (expandedPath.startsWith("~")) {
        if (expandedPath === "~" || expandedPath.startsWith("~/")) {
          // Current user home directory
          expandedPath = expandedPath.replace("~", os.homedir());
        } else {
          // Other user directory (~username)
          const tildeMatch = expandedPath.match(/^~([^/]+)/);
          if (tildeMatch) {
            const username = tildeMatch[1];
            // On Unix systems, try to resolve other user's home directory
            if (os.platform() !== "win32") {
              try {
                const { execSync } = require("node:child_process");
                const userHome = execSync(`eval echo ~${username}`, {
                  encoding: "utf8",
                  timeout: 2000,
                }).trim();
                if (userHome && userHome !== `~${username}`) {
                  expandedPath = expandedPath.replace(`~${username}`, userHome);
                }
              } catch {
                // If can't resolve other user, leave as is
                logger.debug("Could not resolve user home directory", {
                  username,
                  originalPath: commandPath,
                });
              }
            }
          }
        }
      }

      // Handle environment variable expansion
      // Support both $VAR and ${VAR} syntax
      expandedPath = expandedPath.replace(
        /\$\{([^}]+)\}/g,
        (match, varName) => {
          return env[varName] || match;
        },
      );

      expandedPath = expandedPath.replace(
        /\$([A-Za-z_][A-Za-z0-9_]*)/g,
        (match, varName) => {
          return env[varName] || match;
        },
      );

      // Convert to absolute path if it's relative (but not if it's just a command name)
      if (
        !path.isAbsolute(expandedPath) &&
        (expandedPath.includes("/") || expandedPath.includes("\\"))
      ) {
        // Use current working directory as base for relative paths
        expandedPath = path.resolve(process.cwd(), expandedPath);
      }

      logger.debug("Expanded path", {
        original: commandPath,
        expanded: expandedPath,
        wasChanged: commandPath !== expandedPath,
      });

      return expandedPath;
    } catch (error) {
      logger.warn("Failed to expand path, using original", {
        path: commandPath,
        error: error,
      });
      return commandPath;
    }
  }

  /**
   * Resolve command to full executable path with cross-platform support.
   * Supports Windows (.exe, .cmd, .bat), macOS and Linux executables.
   * Enhanced for macOS to properly resolve symlinks to actual executable paths.
   *
   * @param command - Command to resolve (e.g., 'npx', 'node', 'npm')
   * @param env - Environment variables (used for PATH lookup)
   * @returns Full path to executable or original command if not found
   */
  private resolveCommand(command: string, env: Record<string, string>): string {
    try {
      const platform = os.platform();

      // First, expand any user paths or environment variables in the command
      const expandedCommand = this.expandPath(command.trim(), env);

      const pathEnv = env.PATH || env.Path || env.path || "";
      const pathSeparator = platform === "win32" ? ";" : ":";
      const pathDirs = pathEnv.split(pathSeparator).filter((dir) => dir.trim());

      logger.debug("Starting command resolution", {
        originalCommand: command,
        expandedCommand: expandedCommand,
        platform: platform,
        pathEnv: pathEnv,
        pathDirsCount: pathDirs.length,
        pathDirs: pathDirs.slice(0, 5), // Log first 5 PATH directories for debugging
      });

      // Define executable extensions by platform
      const executableExtensions =
        platform === "win32" ? ["", ".exe", ".cmd", ".bat", ".com"] : [""];

      // Helper function to resolve symlinks and validate executability
      const resolveAndValidatePath = (candidatePath: string): string | null => {
        try {
          if (!fs.existsSync(candidatePath)) {
            return null;
          }

          const stats = fs.statSync(candidatePath);
          if (!stats.isFile()) {
            return null;
          }

          // Check if file is executable (Unix-like systems)
          if (platform !== "win32") {
            try {
              fs.accessSync(candidatePath, fs.constants.X_OK);
            } catch {
              return null; // Not executable
            }
          }

          // For Unix-like systems, resolve symlinks to get the actual executable path
          if (platform !== "win32") {
            try {
              const realPath = fs.realpathSync(candidatePath);
              logger.debug("Resolved symlink", {
                originalPath: candidatePath,
                realPath: realPath,
                isSymlink: realPath !== candidatePath,
              });
              return realPath;
            } catch (symlinkError) {
              logger.debug("Failed to resolve symlink, using original path", {
                path: candidatePath,
                error: symlinkError,
              });
              return candidatePath;
            }
          }

          return candidatePath;
        } catch {
          return null;
        }
      };

      // First, try absolute/relative paths (now with expanded paths)
      if (
        path.isAbsolute(expandedCommand) ||
        expandedCommand.includes(path.sep)
      ) {
        for (const ext of executableExtensions) {
          const fullPath = expandedCommand + ext;
          const resolvedPath = resolveAndValidatePath(fullPath);
          if (resolvedPath) {
            logger.debug("Resolved command using direct expanded path", {
              originalCommand: command,
              expandedCommand: expandedCommand,
              resolvedPath: resolvedPath,
              platform,
            });
            return resolvedPath;
          }
        }
      }

      // Search in PATH directories (use original command name for PATH search)
      const commandName = path.basename(expandedCommand);
      for (const dir of pathDirs) {
        if (!dir.trim()) continue;

        for (const ext of executableExtensions) {
          const fullPath = path.join(dir, commandName + ext);
          const resolvedPath = resolveAndValidatePath(fullPath);
          if (resolvedPath) {
            logger.debug("Resolved command in PATH", {
              originalCommand: command,
              commandName: commandName,
              resolvedPath: resolvedPath,
              searchDir: dir,
              platform,
            });
            return resolvedPath;
          }
        }
      }

      // Special handling for common Node.js and modern package manager commands
      const nodeCommands = [
        "node",
        "npm",
        "npx",
        "yarn",
        "pnpm",
        "bun",
        "bunx",
        "deno",
        "tsx",
        "vite",
        // Python package managers
        "uv",
        "uvx",
        "pipx",
      ];
      if (nodeCommands.includes(command)) {
        logger.debug("Attempting modern runtime command resolution", {
          command,
          platform,
          nodeVersion: process.version,
        });

        // Try Node.js installation detection
        const nodeDir = path.dirname(process.execPath);
        for (const ext of executableExtensions) {
          const nodeCmdPath = path.join(nodeDir, command + ext);
          const resolvedPath = resolveAndValidatePath(nodeCmdPath);
          if (resolvedPath) {
            logger.debug("Resolved Node.js command in Node installation", {
              originalCommand: command,
              resolvedPath: resolvedPath,
              nodeDir,
            });
            return resolvedPath;
          }
        }

        // For macOS/Linux, try common JavaScript runtime installation paths
        if (platform === "darwin" || platform === "linux") {
          const commonJSRuntimePaths = [
            "/usr/local/bin",
            "/opt/homebrew/bin", // Apple Silicon Homebrew
            "/usr/bin",
            path.join(os.homedir(), ".nvm/current/bin"), // NVM
            path.join(os.homedir(), ".node_modules/.bin"), // Global npm
            path.join(os.homedir(), ".bun/bin"), // Bun installation
            path.join(os.homedir(), ".deno/bin"), // Deno installation
            path.join(os.homedir(), ".local/bin"), // Local user binaries
            // mise (formerly rtx) version manager paths
            path.join(os.homedir(), ".local/share/mise/installs/node/*/bin"),
            path.join(os.homedir(), ".local/share/mise/installs/bun/*/bin"),
            path.join(os.homedir(), ".local/share/mise/installs/uv/*/uv-*"), // UV installations
            path.join(os.homedir(), ".local/share/rtx/installs/node/*/bin"),
            // asdf version manager paths
            path.join(os.homedir(), ".asdf/installs/nodejs/*/bin"),
            // fnm (Fast Node Manager) paths
            path.join(os.homedir(), ".fnm/node-versions/*/installation/bin"),
          ];

          // Handle glob patterns for version managers
          for (const pathPattern of commonJSRuntimePaths) {
            if (pathPattern.includes("*")) {
              // Expand glob pattern
              const baseDir = pathPattern.split("*")[0];
              if (fs.existsSync(baseDir)) {
                try {
                  const versionDirs = fs.readdirSync(baseDir);
                  for (const versionDir of versionDirs) {
                    const expandedPath = pathPattern.replace("*", versionDir);
                    if (fs.existsSync(expandedPath)) {
                      for (const ext of executableExtensions) {
                        const jsCmdPath = path.join(
                          expandedPath,
                          command + ext,
                        );
                        const resolvedPath = resolveAndValidatePath(jsCmdPath);
                        if (resolvedPath) {
                          logger.debug(
                            "Resolved JS runtime command in version manager path",
                            {
                              originalCommand: command,
                              resolvedPath: resolvedPath,
                              versionManagerPath: expandedPath,
                              versionDir: versionDir,
                            },
                          );
                          return resolvedPath;
                        }
                      }
                    }
                  }
                } catch (globError) {
                  logger.debug(
                    "Failed to expand version manager glob pattern",
                    {
                      pathPattern,
                      error: globError,
                    },
                  );
                }
              }
            } else {
              // Regular path without glob
              for (const ext of executableExtensions) {
                const jsCmdPath = path.join(pathPattern, command + ext);
                const resolvedPath = resolveAndValidatePath(jsCmdPath);
                if (resolvedPath) {
                  logger.debug("Resolved JS runtime command in common path", {
                    originalCommand: command,
                    resolvedPath: resolvedPath,
                    jsDir: pathPattern,
                  });
                  return resolvedPath;
                }
              }
            }
          }
        }
      }

      // For macOS specifically, try using 'which' command as a fallback
      if (platform === "darwin") {
        try {
          const { execSync } = require("node:child_process");
          const whichResult = execSync(`which ${command}`, {
            encoding: "utf8",
            stdio: ["ignore", "pipe", "ignore"],
            timeout: 5000,
          }).trim();

          if (whichResult && fs.existsSync(whichResult)) {
            const resolvedPath = resolveAndValidatePath(whichResult);
            if (resolvedPath) {
              logger.debug("Resolved command using 'which' on macOS", {
                originalCommand: command,
                resolvedPath: resolvedPath,
              });
              return resolvedPath;
            }
          }
        } catch (whichError) {
          logger.debug("'which' command failed on macOS", {
            command,
            error: whichError,
          });
        }
      }

      logger.warn("Could not resolve command to full path, using original", {
        command,
        platform,
        pathDirs: pathDirs.length,
        searchedExtensions: executableExtensions,
      });

      // Return original command if not found (let system handle it)
      return command;
    } catch (error) {
      logger.error("Error during command resolution", {
        command,
        error,
        platform: os.platform(),
      });

      // Return original command on error
      return command;
    }
  }

  /**
   * Parse STDIO command string into command, arguments and environment variables.
   * Now supports path expansion for user paths and environment variables.
   */
  private parseStdioCommand(
    commandStr: string,
    advancedSettings?: Record<string, unknown> | null,
  ): StdioCommandInfo {
    if (!commandStr.trim()) {
      throw new Error(
        "STDIO command cannot be empty. Please provide a valid command string, e.g., 'npx @example/mcp-server' or 'python server.py'",
      );
    }

    try {
      // Merge environment variables first (system + custom)
      const customEnvVars =
        (advancedSettings?.customEnvVars as Record<string, string>) || {};
      const env = this.mergeEnvironment(customEnvVars);

      // Parse command and arguments from the original string FIRST
      const spaceIndex = commandStr.indexOf(" ");
      const originalCommand =
        spaceIndex === -1
          ? commandStr.trim()
          : commandStr.substring(0, spaceIndex);
      const argsStr =
        spaceIndex === -1 ? "" : commandStr.substring(spaceIndex + 1);
      const args = argsStr
        ? argsStr.split(" ").filter((arg) => arg.trim())
        : [];

      // Validate command name
      if (!originalCommand) {
        throw new Error("Command name cannot be empty");
      }

      // THEN expand the command part only (not the arguments)
      const expandedCommand = this.expandPath(originalCommand, env);

      // Handle special command aliases (like bunx -> bun x)
      let finalCommand = expandedCommand;
      let finalArgs = args;

      // Special handling for bunx - it should become "bun x"
      if (originalCommand === "bunx") {
        finalCommand = "bun";
        finalArgs = ["x", ...args];
        logger.debug("Transformed bunx to bun x", {
          originalCommand: originalCommand,
          finalCommand: finalCommand,
          originalArgs: args,
          finalArgs: finalArgs,
        });
      }

      logger.debug("Parsed command string with expansion", {
        originalCommandStr: commandStr,
        originalCommand: originalCommand,
        expandedCommand: expandedCommand,
        finalCommand: finalCommand,
        args: finalArgs,
        hasCustomEnv: Object.keys(customEnvVars).length > 0,
      });

      // Resolve command to full path using the final command
      const resolvedCommand = this.resolveCommand(finalCommand, env);

      logger.debug("Parsed STDIO command", {
        originalCommand: originalCommand,
        expandedCommand: expandedCommand,
        finalCommand: finalCommand,
        resolvedCommand,
        args: finalArgs,
        hasCustomEnv: Object.keys(customEnvVars).length > 0,
        platform: os.platform(),
      });

      return {
        command: originalCommand,
        resolvedCommand,
        args: finalArgs, // Use finalArgs instead of args
        env,
      };
    } catch (error) {
      // Enhance parsing errors with helpful context
      if (error instanceof Error) {
        throw new Error(
          `STDIO command parsing failed: "${commandStr}".\n\nError details: ${error.message}\n\nPlease check if the command format is correct, e.g.:\n- npx @example/mcp-server\n- python /path/to/server.py\n- node server.js --port 3000`,
        );
      }
      throw error;
    }
  }

  /**
   * Create appropriate transport based on server type
   */
  private createTransport(
    server: McpServer,
  ): StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport {
    switch (server.type) {
      case "stdio": {
        if (!server.command) {
          throw new Error(`STDIO server ${server.name} must have a command`);
        }

        try {
          const cmdInfo = this.parseStdioCommand(
            server.command,
            server.advancedSettings,
          );

          logger.debug("Creating STDIO transport", {
            serverId: server.id,
            serverName: server.name,
            originalCommand: cmdInfo.command,
            resolvedCommand: cmdInfo.resolvedCommand,
            args: cmdInfo.args,
            hasCustomEnv:
              Object.keys(server.advancedSettings?.customEnvVars || {}).length >
              0,
            platform: os.platform(),
            workingDirectory: process.cwd(),
            // Enhanced debug info for packaging issues
            processEnv: {
              NODE_ENV: cmdInfo.env.NODE_ENV,
              PATH: cmdInfo.env.PATH,
              HOME: cmdInfo.env.HOME,
              USER: cmdInfo.env.USER,
              SHELL: cmdInfo.env.SHELL,
            },
            fileExists: fs.existsSync(cmdInfo.resolvedCommand),
            fileStats: fs.existsSync(cmdInfo.resolvedCommand)
              ? fs.statSync(cmdInfo.resolvedCommand)
              : null,
          });

          return new StdioClientTransport({
            command: cmdInfo.resolvedCommand,
            args: cmdInfo.args,
            env: cmdInfo.env,
            stderr: "inherit", // Show stderr output for debugging
            cwd: process.cwd(), // Set working directory
          });
        } catch (error) {
          logger.error("Failed to create STDIO transport", {
            serverId: server.id,
            serverName: server.name,
            command: server.command,
            error: error instanceof Error ? error.message : String(error),
            platform: os.platform(),
          });

          throw this.createStdioCommandError(
            error,
            server.command || "",
            server.name,
          );
        }
      }

      case "sse": {
        if (!server.url) {
          throw new Error(`SSE server ${server.name} must have a URL`);
        }
        const sseOptions: SSEClientTransportOptions = {};

        // Add custom headers if configured
        if (server.advancedSettings?.customHeaders) {
          const customHeaders = server.advancedSettings.customHeaders as Record<
            string,
            string
          >;
          sseOptions.requestInit = {
            headers: customHeaders,
          };
          // Note: EventSource doesn't support custom headers directly,
          // only requestInit for POST requests will use the headers
        }

        return new SSEClientTransport(new URL(server.url), sseOptions);
      }

      case "streamableHTTP": {
        if (!server.url) {
          throw new Error(
            `StreamableHTTP server ${server.name} must have a URL`,
          );
        }
        const httpOptions: StreamableHTTPClientTransportOptions = {};

        // Add custom headers if configured
        if (server.advancedSettings?.customHeaders) {
          const customHeaders = server.advancedSettings.customHeaders as Record<
            string,
            string
          >;
          httpOptions.requestInit = {
            headers: customHeaders,
          };
        }

        return new StreamableHTTPClientTransport(
          new URL(server.url),
          httpOptions,
        );
      }

      default:
        throw new Error(`Unsupported server type: ${server.type}`);
    }
  }

  /**
   * Convert MCP tool parameters to OpenAI function parameters format
   */
  private convertMcpParametersToOpenAI(
    parameters: unknown,
  ): Record<string, unknown> {
    if (!parameters || typeof parameters !== "object") {
      return {
        type: "object",
        properties: {},
        required: [],
        additionalProperties: false,
      };
    }

    const params = parameters as Record<string, unknown>;

    // If it's already in OpenAI format, return as is
    if (params.type === "object" && params.properties) {
      return {
        type: "object",
        properties: params.properties,
        required: params.required || [],
        additionalProperties: params.additionalProperties ?? false,
      };
    }

    // Convert MCP JSON schema to OpenAI format
    const properties: Record<string, unknown> = {};
    const required: string[] = [];

    // If parameters is a flat object, treat each key as a property
    for (const [key, value] of Object.entries(params)) {
      if (typeof value === "object" && value !== null) {
        const prop = value as Record<string, unknown>;
        properties[key] = {
          type: prop.type || "string",
          description: prop.description || `Parameter: ${key}`,
        };

        // Add to required if marked as required
        if (prop.required === true) {
          required.push(key);
        }
      } else {
        // Simple property
        properties[key] = {
          type: "string",
          description: `Parameter: ${key}`,
        };
      }
    }

    return {
      type: "object",
      properties,
      required,
      additionalProperties: false,
    };
  }

  // Thread-specific MCP server management methods
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async addMcpServerToThread(
    _event: Electron.IpcMainEvent,
    threadId: string,
    mcpServerId: string,
    options?: { enabled?: boolean; order?: number },
  ): Promise<void> {
    try {
      logger.info("Adding MCP server to thread", {
        threadId,
        mcpServerId,
        options,
      });

      await this.threadMcpServerDbService.addMcpServerToThread(
        threadId,
        mcpServerId,
        options,
      );

      logger.info("MCP server added to thread successfully", {
        threadId,
        mcpServerId,
      });
    } catch (err) {
      logger.error("Failed to add MCP server to thread", {
        error: err,
        threadId,
        mcpServerId,
      });
      throw err;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async removeMcpServerFromThread(
    _event: Electron.IpcMainEvent,
    threadId: string,
    mcpServerId: string,
  ): Promise<void> {
    try {
      logger.info("Removing MCP server from thread", {
        threadId,
        mcpServerId,
      });

      await this.threadMcpServerDbService.removeMcpServerFromThread(
        threadId,
        mcpServerId,
      );

      logger.info("MCP server removed from thread successfully", {
        threadId,
        mcpServerId,
      });
    } catch (err) {
      logger.error("Failed to remove MCP server from thread", {
        error: err,
        threadId,
        mcpServerId,
      });
      throw err;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getMcpServersByThreadId(
    _event: Electron.IpcMainEvent,
    threadId: string,
  ): Promise<McpServer[]> {
    try {
      logger.debug("Getting MCP servers for thread", { threadId });
      return await this.threadMcpServerDbService.getMcpServersByThreadId(
        threadId,
      );
    } catch (err) {
      logger.error("Failed to get MCP servers for thread", {
        error: err,
        threadId,
      });
      throw err;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async getEnabledMcpServersByThreadId(
    _event: Electron.IpcMainEvent,
    threadId: string,
  ): Promise<McpServer[]> {
    try {
      logger.debug("Getting enabled MCP servers for thread", { threadId });
      return await this.threadMcpServerDbService.getEnabledMcpServersByThreadId(
        threadId,
      );
    } catch (err) {
      logger.error("Failed to get enabled MCP servers for thread", {
        error: err,
        threadId,
      });
      throw err;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async setThreadMcpServers(
    _event: Electron.IpcMainEvent,
    threadId: string,
    serverIds: string[],
  ): Promise<void> {
    try {
      logger.info("Setting MCP servers for thread", {
        threadId,
        serverIds,
      });

      await this.threadMcpServerDbService.setThreadMcpServers(
        threadId,
        serverIds,
      );

      logger.info("MCP servers set for thread successfully", {
        threadId,
        count: serverIds.length,
      });
    } catch (err) {
      logger.error("Failed to set MCP servers for thread", {
        error: err,
        threadId,
        serverIds,
      });
      throw err;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async updateThreadMcpServerSettings(
    _event: Electron.IpcMainEvent,
    threadId: string,
    mcpServerId: string,
    settings: { enabled?: boolean; order?: number },
  ): Promise<void> {
    try {
      logger.info("Updating thread MCP server settings", {
        threadId,
        mcpServerId,
        settings,
      });

      await this.threadMcpServerDbService.updateThreadMcpServer(
        threadId,
        mcpServerId,
        settings,
      );

      logger.info("Thread MCP server settings updated successfully", {
        threadId,
        mcpServerId,
      });
    } catch (err) {
      logger.error("Failed to update thread MCP server settings", {
        error: err,
        threadId,
        mcpServerId,
        settings,
      });
      throw err;
    }
  }

  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async reorderThreadMcpServers(
    _event: Electron.IpcMainEvent,
    threadId: string,
    serverOrders: Array<{ serverId: string; order: number }>,
  ): Promise<void> {
    try {
      logger.info("Reordering MCP servers for thread", {
        threadId,
        serverOrders,
      });

      await this.threadMcpServerDbService.reorderThreadMcpServers(
        threadId,
        serverOrders,
      );

      logger.info("MCP servers reordered for thread successfully", {
        threadId,
        count: serverOrders.length,
      });
    } catch (err) {
      logger.error("Failed to reorder MCP servers for thread", {
        error: err,
        threadId,
        serverOrders,
      });
      throw err;
    }
  }

  /**
   * Get server IDs for a thread (helper method for tool calling)
   */
  async getServerIdsForThread(threadId: string): Promise<string[]> {
    try {
      const servers =
        await this.threadMcpServerDbService.getEnabledMcpServersByThreadId(
          threadId,
        );
      return servers.map((server) => server.id);
    } catch (err) {
      logger.error("Failed to get server IDs for thread", {
        error: err,
        threadId,
      });
      return [];
    }
  }

  /**
   * Test connection to an MCP server and retrieve its tools without persisting the connection
   * Used for validation before saving server configurations
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__TWO_WAY)
  async testMcpServerConnection(
    _event: Electron.IpcMainEvent,
    serverData: CreateMcpServerData,
  ): Promise<{
    success: boolean;
    errorMessage: string | null;
    tools?: McpTool[];
  }> {
    let tempClient: Client | null = null;
    let tempTransport:
      | StdioClientTransport
      | SSEClientTransport
      | StreamableHTTPClientTransport
      | null = null;

    try {
      logger.info("Testing MCP server connection", {
        name: serverData.name,
        type: serverData.type,
        url: serverData.url,
      });

      // Create a temporary server object for transport creation
      const tempServer: McpServer = {
        id: `temp-${Date.now()}`,
        name: serverData.name,
        description: serverData.description || "",
        type: serverData.type,
        url: serverData.url,
        command: serverData.command,
        icon: serverData.icon || "",
        enabled: true,
        order: 0,
        advancedSettings: serverData.advancedSettings,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      // Create transport and client
      tempTransport = this.createTransport(tempServer);
      tempClient = new Client({
        name: "302-AI-Studio-Test",
        version: "1.0.0",
      });

      // Set a timeout for the connection attempt (0 means no timeout)
      const timeoutMs =
        serverData.advancedSettings?.timeout != null
          ? serverData.advancedSettings.timeout
          : 30000;

      const connectionPromise = tempClient.connect(tempTransport);

      if (timeoutMs === 0) {
        // No timeout - wait indefinitely
        await connectionPromise;
      } else {
        // Use timeout
        const timeoutPromise = new Promise((_, reject) =>
          setTimeout(() => reject(new Error("Connection timeout")), timeoutMs),
        );
        await Promise.race([connectionPromise, timeoutPromise]);
      }

      // Try to list tools to verify the connection works
      const toolsResult = await tempClient.listTools();
      const tools: McpTool[] = toolsResult.tools.map((tool) => ({
        id: `${tempServer.id}-${tool.name}`,
        name: tool.name,
        description: tool.description || "",
        parameters: tool.inputSchema || {},
        mcpServerId: tempServer.id,
        enabled: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      }));

      logger.info("MCP server connection test successful", {
        name: serverData.name,
        toolCount: tools.length,
      });

      return {
        success: true,
        errorMessage: null,
        tools,
      };
    } catch (err) {
      logger.error("MCP server connection test failed", {
        name: serverData.name,
        error: err,
      });

      return {
        success: false,
        errorMessage: this.extractErrorMessage(
          err,
          "MCP Server Connection Test",
        ),
      };
    } finally {
      // Clean up temporary client and transport
      if (tempClient) {
        try {
          await tempClient.close();
        } catch (cleanupErr) {
          logger.warn("Failed to close temporary MCP client", {
            error: cleanupErr,
          });
        }
      }

      if (tempTransport) {
        try {
          await tempTransport.close();
        } catch (cleanupErr) {
          logger.warn("Failed to close temporary MCP transport", {
            error: cleanupErr,
          });
        }
      }
    }
  }

  /**
   * Get MCP client instance for tool execution
   */
  getMcpClientForTool(serverId: string): {
    client: Client;
    connected: boolean;
  } | null {
    const clientInfo = this.mcpClients.get(serverId);
    if (!clientInfo) {
      return null;
    }

    return {
      client: clientInfo.client,
      connected: clientInfo.connected,
    };
  }

  /**
   * Get tools from database cache for a specific server
   */
  async getToolsFromDatabase(serverId: string): Promise<
    Array<{
      name: string;
      description?: string;
      inputSchema?: Record<string, unknown>;
    }>
  > {
    try {
      const dbTools =
        await this.mcpToolDbService.getEnabledMcpToolsByServerId(serverId);

      return dbTools.map((tool) => ({
        name: tool.name,
        description: tool.description,
        inputSchema: tool.parameters,
      }));
    } catch (error) {
      logger.error("Failed to get tools from database", {
        serverId,
        error: this.extractErrorMessage(error, "Database Query"),
      });
      throw error;
    }
  }

  /**
   * Handle tool call approval from renderer
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async approveTool(
    _event: Electron.IpcMainEvent,
    threadId: string,
    messageId: string,
    toolCallId: string,
  ): Promise<void> {
    logger.info("Tool call approved via IPC - starting UI update", {
      threadId,
      messageId,
      toolCallId,
    });

    // Update the UI immediately to show execution state
    await this.updateToolCallStatusInUI(
      threadId,
      messageId,
      toolCallId,
      "pending",
    );

    logger.info("Tool call UI updated, emitting approval event", {
      threadId,
      messageId,
      toolCallId,
    });

    emitter.emit(EventNames.TOOL_CALL_APPROVED, {
      threadId,
      messageId,
      toolCallId,
    });
  }

  /**
   * Update tool call status in the UI immediately
   * This provides instant feedback when user approves a tool call
   */
  private async updateToolCallStatusInUI(
    threadId: string,
    messageId: string,
    toolCallId: string,
    newStatus: "pending" | "success" | "error",
  ): Promise<void> {
    try {
      logger.info("Starting updateToolCallStatusInUI", {
        threadId,
        messageId,
        toolCallId,
        newStatus,
      });

      // Get the message to access its sequential content
      const mockEvent = {} as Electron.IpcMainEvent;
      const message = await this.messageService.getMessageById(
        mockEvent,
        messageId,
      );
      if (!message?.contentBlocks) {
        logger.warn(
          "Message or sequential content not found for immediate status update",
          {
            messageId,
            toolCallId,
            hasMessage: !!message,
            hasContentBlocks: !!message?.contentBlocks,
          },
        );
        return;
      }

      logger.info("Found message with content blocks, parsing...", {
        messageId,
        contentBlocksLength: message.contentBlocks.length,
      });

      const sequentialContent: SequentialMessageContent = JSON.parse(
        message.contentBlocks,
      );

      logger.info("Parsed sequential content", {
        messageId,
        blocksCount: sequentialContent.blocks.length,
        blockTypes: sequentialContent.blocks.map((b) => ({
          id: b.id,
          type: b.type,
        })),
      });

      // Find the tool call block
      const toolCallBlock = sequentialContent.blocks.find(
        (block: ContentBlock) =>
          block.type === "tool_call" &&
          (block as ToolCallContentBlock).toolCall.id === toolCallId,
      ) as ToolCallContentBlock | undefined;

      if (!toolCallBlock || toolCallBlock.type !== "tool_call") {
        logger.warn("Tool call block not found for immediate status update", {
          messageId,
          toolCallId,
          availableBlocks: sequentialContent.blocks.map((b: ContentBlock) => ({
            id: b.id,
            type: b.type,
            toolCallId:
              b.type === "tool_call"
                ? (b as ToolCallContentBlock).toolCall.id
                : null,
          })),
        });
        return;
      }

      logger.info("Found tool call block, updating status", {
        blockId: toolCallBlock.id,
        currentStatus: toolCallBlock.toolCall.status,
        newStatus,
        currentIsExecuting: toolCallBlock.isExecuting,
      });

      // Create updated tool call with new status
      const updatedToolCall = {
        ...toolCallBlock.toolCall,
        status: newStatus,
        requiresApproval: false, // Clear approval requirement
      };

      // Update the block in the sequential content
      toolCallBlock.toolCall = updatedToolCall;
      toolCallBlock.isExecuting = newStatus === "pending";

      // Save updated content back to the message
      await this.messageService.updateMessage(mockEvent, messageId, {
        contentBlocks: JSON.stringify(sequentialContent),
      });

      logger.info("Updated database with new tool call status", {
        threadId,
        messageId,
        toolCallId,
        newStatus,
        blockId: toolCallBlock.id,
        updatedToolCall,
        isExecuting: toolCallBlock.isExecuting,
      });

      // Send update event to renderer
      const eventData = {
        threadId,
        messageId,
        blockId: toolCallBlock.id,
        updateData: {
          toolCall: updatedToolCall,
          isExecuting: toolCallBlock.isExecuting,
        },
      };

      logger.info("Sending CONTENT_BLOCK_UPDATE event to renderer", {
        eventType: EventNames.CONTENT_BLOCK_UPDATE,
        eventData,
      });

      sendToRenderer(EventNames.CONTENT_BLOCK_UPDATE, eventData);

      logger.info("Successfully completed updateToolCallStatusInUI", {
        threadId,
        messageId,
        toolCallId,
        newStatus,
      });
    } catch (error) {
      logger.error("Failed to update tool call status in UI", {
        error,
        threadId,
        messageId,
        toolCallId,
        newStatus,
      });
      throw error; // Re-throw to let caller know there was an issue
    }
  }

  /**
   * Handle tool call rejection from renderer
   */
  @ServiceHandler(CommunicationWay.RENDERER_TO_MAIN__ONE_WAY)
  async rejectTool(
    _event: Electron.IpcMainEvent,
    threadId: string,
    messageId: string,
    toolCallId: string,
    reason?: string,
  ): Promise<void> {
    logger.info("Tool call rejected via IPC", {
      threadId,
      messageId,
      toolCallId,
      reason,
    });

    // Update the UI immediately to show rejection state
    await this.updateToolCallStatusInUI(
      threadId,
      messageId,
      toolCallId,
      "error",
    );

    emitter.emit(EventNames.TOOL_CALL_REJECTED, {
      threadId,
      messageId,
      toolCallId,
      reason,
    });
  }

  /**
   * Extract and enhance error message from error object with specific error type handling
   */
  private extractErrorMessage(error: unknown, context?: string): string {
    if (error instanceof Error) {
      const message = error.message;

      // Handle specific error types with clear user-friendly messages

      // Command not found errors
      if (
        message.includes("ENOENT") ||
        (message.includes("spawn") && message.includes("ENOENT"))
      ) {
        const commandMatch = message.match(/spawn ([^\s]+)/);
        const command = commandMatch ? commandMatch[1] : "command";
        return `Executable not found: ${command}. Please check:\n1. Command is properly installed\n2. PATH environment variable includes the command's path\n3. Command path is spelled correctly`;
      }

      // Permission errors
      if (message.includes("EACCES") || message.includes("permission denied")) {
        return `Permission denied: Cannot execute the command. Please check:\n1. File has execute permissions\n2. Current user has access to the path\n3. On macOS/Linux, run chmod +x to grant execute permissions`;
      }

      // Connection timeout errors
      if (message.includes("timeout") || message.includes("ETIMEDOUT")) {
        return `Connection timeout: Unable to establish connection within the specified time. Please check:\n1. Network connection is working\n2. Server address is correct\n3. Server is running\n4. Firewall is not blocking the connection`;
      }

      // Connection refused errors
      if (
        message.includes("ECONNREFUSED") ||
        message.includes("Connection refused")
      ) {
        return `Connection refused: Target server rejected the connection. Please check:\n1. Server is running\n2. Port number is correct\n3. Server is listening on the specified address and port`;
      }

      // Network unreachable errors
      if (
        message.includes("ENETUNREACH") ||
        message.includes("Network unreachable")
      ) {
        return `Network unreachable: Cannot reach the target server. Please check:\n1. Network connection is working\n2. DNS settings are correct\n3. Proxy settings are correct`;
      }

      // DNS resolution errors
      if (message.includes("ENOTFOUND") || message.includes("getaddrinfo")) {
        const hostMatch = message.match(/getaddrinfo ENOTFOUND ([^\s]+)/);
        const host = hostMatch ? hostMatch[1] : "host";
        return `DNS resolution failed: Cannot resolve hostname "${host}". Please check:\n1. Hostname spelling is correct\n2. DNS server settings are correct\n3. Network connection is working`;
      }

      // SSL/TLS certificate errors
      if (message.includes("CERT_") || message.includes("certificate")) {
        return `SSL/TLS certificate error: Certificate validation failed. Please check:\n1. Server certificate is valid\n2. System time is correct\n3. Self-signed certificates may need to be trusted`;
      }

      // HTTP status code errors
      const httpStatusMatch = message.match(/(\d{3})/);
      if (httpStatusMatch) {
        const statusCode = parseInt(httpStatusMatch[1], 10);
        if (statusCode >= 400 && statusCode < 500) {
          return `Client error (${statusCode}): Request was invalid. Please check:\n1. URL address is correct\n2. Authentication information is correct\n3. Request parameters meet server requirements`;
        }
        if (statusCode >= 500) {
          return `Server error (${statusCode}): Internal server error. Please check:\n1. Server is running properly\n2. Try again later\n3. Contact server administrator`;
        }
      }

      // Invalid URL errors
      if (
        message.includes("Invalid URL") ||
        message.includes("TypeError: Invalid URL")
      ) {
        return `Invalid URL: URL format is incorrect. Please check:\n1. URL includes protocol (http:// or https://)\n2. URL format is correct\n3. Special characters may need encoding`;
      }

      // Port binding errors
      if (
        message.includes("EADDRINUSE") ||
        message.includes("address already in use")
      ) {
        return `Port already in use: Specified port is occupied. Please check:\n1. Use a different available port\n2. Close other programs using this port\n3. Check if other MCP servers are running`;
      }

      // File system errors
      if (
        message.includes("EMFILE") ||
        message.includes("too many open files")
      ) {
        return `System resource exhaustion: Too many open file handles. Please check:\n1. Close unnecessary applications\n2. Restart the application\n3. Check system file handle limits`;
      }

      // Memory errors
      if (
        message.includes("out of memory") ||
        message.includes("Cannot allocate memory")
      ) {
        return `Insufficient memory: System memory is low. Please check:\n1. Close unnecessary applications to free memory\n2. Check system memory usage\n3. Restart the application`;
      }

      // JSON parsing errors
      if (
        message.includes("JSON") &&
        (message.includes("parse") || message.includes("Unexpected"))
      ) {
        return `Data format error: Server returned incorrect data format. Please check:\n1. Server properly implements MCP protocol\n2. Network transmission is complete\n3. Server version is compatible`;
      }

      // Protocol errors
      if (message.includes("protocol") || message.includes("Protocol")) {
        return `Protocol error: MCP protocol communication failed. Please check:\n1. Server properly implements MCP protocol\n2. Protocol versions are compatible\n3. Message format is correct`;
      }

      // Path resolution errors
      if (message.includes("path") && message.includes("resolve")) {
        return `Path resolution error: Cannot resolve specified path. Please check:\n1. Path exists\n2. Path format is correct\n3. Permissions to access the path`;
      }

      // Environment variable errors
      if (message.includes("environment") || message.includes("env")) {
        return `Environment variable error: Environment configuration is incorrect. Please check:\n1. Required environment variables are set\n2. PATH environment variable includes necessary paths\n3. Environment variable values are correct`;
      }

      // Return enhanced message with context if available
      const contextPrefix = context ? `[${context}] ` : "";
      return `${contextPrefix}${message}`;
    }

    if (typeof error === "string") {
      return error;
    }

    // Last resort - try to stringify the error object
    try {
      return JSON.stringify(error);
    } catch {
      return "Unknown error occurred, unable to get detailed information";
    }
  }

  /**
   * Create specific error message for STDIO command parsing failures
   */
  private createStdioCommandError(
    error: unknown,
    command: string,
    serverName: string,
  ): Error {
    const baseMessage = this.extractErrorMessage(
      error,
      "STDIO Command Parsing",
    );

    let specificMessage = `STDIO server "${serverName}" command execution failed:\n\nCommand: ${command}\n\n`;

    if (typeof error === "object" && error !== null && "message" in error) {
      const errorMessage = (error as Error).message;

      // Provide specific guidance based on error type
      if (errorMessage.includes("ENOENT")) {
        specificMessage += `Issue: Executable file not found\n\nSuggested solutions:\n`;
        specificMessage += `1. Ensure the command is properly installed (e.g., npm install -g, brew install, apt install)\n`;
        specificMessage += `2. Check that PATH environment variable includes the command's installation path\n`;
        specificMessage += `3. Try using the full path instead of just the command name\n`;
        specificMessage += `4. For Node.js tools, ensure Node.js is properly installed\n\n`;
      } else if (errorMessage.includes("EACCES")) {
        specificMessage += `Issue: Insufficient permissions to execute the command\n\nSuggested solutions:\n`;
        specificMessage += `1. Check if the file has execute permissions\n`;
        specificMessage += `2. On macOS/Linux, run: chmod +x <file-path>\n`;
        specificMessage += `3. Ensure the current user has permission to access the file\n\n`;
      } else if (errorMessage.includes("spawn")) {
        specificMessage += `Issue: Process startup failed\n\nSuggested solutions:\n`;
        specificMessage += `1. Check if the command syntax is correct\n`;
        specificMessage += `2. Ensure all required dependencies are installed\n`;
        specificMessage += `3. Test by running the command manually in terminal\n\n`;
      }
    }

    specificMessage += `Detailed error: ${baseMessage}`;

    return new Error(specificMessage);
  }
}
